package udp

import (
	"errors"
	"net"

	"gitee.com/hasika/gotool/container"

	"gitee.com/hasika/common-server/conn/impl_base"
	"gitee.com/hasika/common-server/conn/inet"
	"gitee.com/hasika/common-server/option"
)

type Listener struct {
	listenConn    *net.UDPConn
	connectionMap container.MapInterface[string, inet.Conn]
	closed        bool
}

func NewServerListener(address string, serviceOption *option.ServiceOption) (*Listener, error) {
	udpAddr, err := net.ResolveUDPAddr("udp", address)
	if err != nil {
		return nil, err
	}
	ln, err := impl_base.ListenWithRetry(func(address string) (*net.UDPConn, error) {
		ln, err := net.ListenUDP("udp", udpAddr)
		if err != nil {
			return nil, err
		}
		return ln, nil
	}, address, serviceOption.ListenRetryOption())
	if err != nil {
		return nil, err
	}
	return NewUdpListener(ln), nil
}

func NewUdpListener(conn *net.UDPConn) *Listener {
	ul := &Listener{
		listenConn:    conn,
		connectionMap: container.NewSyncMap[string, inet.Conn](),
	}
	return ul
}

func (u *Listener) Accept() (inet.Conn, error) {
	if u.closed {
		return nil, errors.New("listener closed")
	}
	buff := make([]byte, 4096)
	n, con, err := u.listenConn.ReadFromUDP(buff)
	if err != nil {
		return nil, err
	}
	address := con.String()
	udpConn, ex := u.connectionMap.Load(address)
	if !ex {
		udpConn = NewServerConnection(con, u)
		u.connectionMap.Store(address, udpConn)
	}
	readCtx := udpConn.GetReadCtx()
	readCtx.Buffer().Write(buff[:n])
	if !ex {
		return udpConn, nil
	}
	return nil, nil
}

func (u *Listener) Close() {
	u.closed = true
	inet.Log.Infof("udp listener stopped")
}
